package com.zaicheng.cloud.remotecall;


import com.zaicheng.cloud.netty.RpcRequest;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public interface Contract {
    List<MethodMetadata> parseAndValidateMetadata(Class<?> var1);

    public static class Default extends DeclarativeContract {
        static final Pattern REQUEST_LINE_PATTERN = Pattern.compile("^([A-Z]+)[ ]*(.*)$");

        public Default() {

            super.registerClassAnnotation(Headers.class, (header, data) -> {
                String[] headersOnType = header.value();
                Util.checkState(headersOnType.length > 0, "Headers annotation was empty on type %s.", new Object[]{data.configKey()});
                Map<String, Collection<String>> headers = toMap(headersOnType);
                headers.putAll(data.template().headers());
                data.template().headers((Map)null);
                data.template().headers(headers);
            });

//            super.registerMethodAnnotation(RequestLine.class, (ann, data) -> {
//                String requestLine = ann.value();
//                Util.checkState(Util.emptyToNull(requestLine) != null, "RequestLine annotation was empty on method %s.", new Object[]{data.configKey()});
//                Matcher requestLineMatcher = REQUEST_LINE_PATTERN.matcher(requestLine);
//                if (!requestLineMatcher.find()) {
//                    throw new IllegalStateException(String.format("RequestLine annotation didn't start with an HTTP verb on method %s", data.configKey()));
//                } else {
//                    data.template().method(RpcRequest.HttpMethod.valueOf(requestLineMatcher.group(1)));
//                    data.template().uri(requestLineMatcher.group(2));
////                    data.template().decodeSlash(ann.decodeSlash());
//                    //data.template().collectionFormat(ann.collectionFormat());
//                }
//            });

            super.registerMethodAnnotation(Body.class, (ann, data) -> {
                String body = ann.value();
                Util.checkState(Util.emptyToNull(body) != null, "Body annotation was empty on method %s.", new Object[]{data.configKey()});
                if (body.indexOf(123) == -1) {
                    data.template().body(body);
                } else {
                    data.template().bodyTemplate(body);
                }
            });

            super.registerMethodAnnotation(Headers.class, (header, data) -> {
                String[] headersOnMethod = header.value();
                Util.checkState(headersOnMethod.length > 0, "Headers annotation was empty on method %s.", new Object[]{data.configKey()});
                data.template().headers(toMap(headersOnMethod));
            });

            super.registerParameterAnnotation(Param.class, (paramAnnotation, data, paramIndex) -> {
                String name = paramAnnotation.value();
                Util.checkState(Util.emptyToNull(name) != null, "Param annotation was empty on param %s.", new Object[]{paramIndex});
                this.nameParam(data, name, paramIndex);
                Class<? extends Param.Expander> expander = paramAnnotation.expander();
                if (expander != Param.ToStringExpander.class) {
                    data.indexToExpanderClass().put(paramIndex, expander);
                }

                if (!data.template().hasRequestVariable(name)) {
                    data.formParams().add(name);
                }
            });

//            super.registerParameterAnnotation(QueryMap.class, (queryMap, data, paramIndex) -> {
//                Util.checkState(data.queryMapIndex() == null, "QueryMap annotation was present on multiple parameters.", new Object[0]);
//                data.queryMapIndex(paramIndex);
//                data.queryMapEncoded(queryMap.encoded());
//            });
//
//            super.registerParameterAnnotation(HeaderMap.class, (queryMap, data, paramIndex) -> {
//                Util.checkState(data.headerMapIndex() == null, "HeaderMap annotation was present on multiple parameters.", new Object[0]);
//                data.headerMapIndex(paramIndex);
//            });
        }

        private static Map<String, Collection<String>> toMap(String[] input) {
            Map<String, Collection<String>> result = new LinkedHashMap(input.length);
            String[] var2 = input;
            int var3 = input.length;

            for(int var4 = 0; var4 < var3; ++var4) {
                String header = var2[var4];
                int colon = header.indexOf(58);
                String name = header.substring(0, colon);
                if (!result.containsKey(name)) {
                    result.put(name, new ArrayList(1));
                }

                ((Collection)result.get(name)).add(header.substring(colon + 1).trim());
            }

            return result;
        }
    }

    public abstract static class BaseContract implements Contract {
        public BaseContract() {
        }

        @Override
        public List<MethodMetadata> parseAndValidateMetadata(Class<?> targetType) {
            Util.checkState(targetType.getTypeParameters().length == 0, "Parameterized types unsupported: %s", new Object[]{targetType.getSimpleName()});
            Util.checkState(targetType.getInterfaces().length <= 1, "Only single inheritance supported: %s", new Object[]{targetType.getSimpleName()});
            if (targetType.getInterfaces().length == 1) {
                Util.checkState(targetType.getInterfaces()[0].getInterfaces().length == 0, "Only single-level inheritance supported: %s", new Object[]{targetType.getSimpleName()});
            }

            Map<String, MethodMetadata> result = new LinkedHashMap();
            Method[] var3 = targetType.getMethods();
            int var4 = var3.length;

            for(int var5 = 0; var5 < var4; ++var5) {
                Method method = var3[var5];
                if (method.getDeclaringClass() != Object.class && (method.getModifiers() & 8) == 0 && !Util.isDefault(method)) {
                    MethodMetadata metadata = this.parseAndValidateMetadata(targetType, method);
                    Util.checkState(!result.containsKey(metadata.configKey()), "Overrides unsupported: %s", new Object[]{metadata.configKey()});
                    result.put(metadata.configKey(), metadata);
                }
            }
            return new ArrayList(result.values());
        }

        /** @deprecated */
        @Deprecated
        public MethodMetadata parseAndValidateMetadata(Method method) {
            return this.parseAndValidateMetadata(method.getDeclaringClass(), method);
        }

        protected MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
            MethodMetadata data = new MethodMetadata();
            data.targetType(targetType);
            data.method(method);
            data.returnType(Types.resolve(targetType, targetType, method.getGenericReturnType()));
            data.configKey(RemoteCall.configKey(targetType, method));
            if (targetType.getInterfaces().length == 1) {
                this.processAnnotationOnClass(data, targetType.getInterfaces()[0]);
            }

            this.processAnnotationOnClass(data, targetType);
            Annotation[] var4 = method.getAnnotations();
            int var5 = var4.length;

            for(int var6 = 0; var6 < var5; ++var6) {
                Annotation methodAnnotation = var4[var6];
                this.processAnnotationOnMethod(data, methodAnnotation, method);
            }

            if (data.isIgnored()) {
                return data;
            } else {
                //Util.checkState(data.template().method() != null, "Method %s not annotated with HTTP method type (ex. GET, POST)%s", new Object[]{data.configKey(), data.warnings()});
                Class<?>[] parameterTypes = method.getParameterTypes();
                Type[] genericParameterTypes = method.getGenericParameterTypes();
                Annotation[][] parameterAnnotations = method.getParameterAnnotations();
                int count = parameterAnnotations.length;

                for(int i = 0; i < count; ++i) {
                    boolean isHttpAnnotation = false;
                    if (parameterAnnotations[i] != null) {
                        isHttpAnnotation = this.processAnnotationsOnParameter(data, parameterAnnotations[i], i);
                    }

//                    if (isHttpAnnotation) {
//                        data.ignoreParamater(i);
//                    }

                    if (parameterTypes[i] == URI.class) {
                        data.urlIndex(i);
                    } else if (!isHttpAnnotation && parameterTypes[i] != RpcRequest.Options.class) {
                        if (!data.isAlreadyProcessed(i)) {
                            Util.checkState(data.formParams().isEmpty(), "Body parameters cannot be used with form parameters.%s", new Object[]{data.warnings()});
                            //Util.checkState(data.bodyIndex() == null, "Method has too many Body parameters: %s%s", new Object[]{method, data.warnings()});
                            data.bodyIndex(i);
                            data.bodyType(Types.resolve(targetType, targetType, genericParameterTypes[i]));
                        } else {
                            Util.checkState(data.formParams().isEmpty() || data.bodyIndex() == null, "Body parameters cannot be used with form parameters.%s", new Object[]{data.warnings()});
                        }
                    }
                }

//                if (data.headerMapIndex() != null) {
//                    checkMapString("HeaderMap", parameterTypes[data.headerMapIndex()], genericParameterTypes[data.headerMapIndex()]);
//                }

                if (data.queryMapIndex() != null && Map.class.isAssignableFrom(parameterTypes[data.queryMapIndex()])) {
                    checkMapKeys("QueryMap", genericParameterTypes[data.queryMapIndex()]);
                }
                return data;
            }
        }

        private static void checkMapString(String name, Class<?> type, Type genericType) {
            Util.checkState(Map.class.isAssignableFrom(type), "%s parameter must be a Map: %s", new Object[]{name, type});
            checkMapKeys(name, genericType);
        }

        private static void checkMapKeys(String name, Type genericType) {
            Class<?> keyClass = null;
            Type[] interfaces;
            if (ParameterizedType.class.isAssignableFrom(genericType.getClass())) {
                interfaces = ((ParameterizedType)genericType).getActualTypeArguments();
                keyClass = (Class)interfaces[0];
            } else if (genericType instanceof Class) {
                interfaces = ((Class)genericType).getGenericInterfaces();
                if (interfaces != null) {
                    Type[] var4 = interfaces;
                    int var5 = interfaces.length;
                    for(int var6 = 0; var6 < var5; ++var6) {
                        Type extended = var4[var6];
                        if (ParameterizedType.class.isAssignableFrom(extended.getClass())) {
                            Type[] parameterTypes = ((ParameterizedType)extended).getActualTypeArguments();
                            keyClass = (Class)parameterTypes[0];
                            break;
                        }
                    }
                }
            }
            if (keyClass != null) {
                Util.checkState(String.class.equals(keyClass), "%s key must be a String: %s", new Object[]{name, keyClass.getSimpleName()});
            }

        }

        protected abstract void processAnnotationOnClass(MethodMetadata var1, Class<?> var2);

        protected abstract void processAnnotationOnMethod(MethodMetadata var1, Annotation var2, Method var3);

        protected abstract boolean processAnnotationsOnParameter(MethodMetadata var1, Annotation[] var2, int var3);

        protected void nameParam(MethodMetadata data, String name, int i) {
            Collection<String> names = data.indexToName().containsKey(i) ? (Collection)data.indexToName().get(i) : new ArrayList();
            ((Collection)names).add(name);
            data.indexToName().put(i, names);
        }
    }
}
